﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using static KMeansDemo.common;

namespace KMeansDemo
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        Random random = new Random(0);

        List<Ellipse> centroids = new List<Ellipse>();
        List<Ellipse> points = new List<Ellipse>();
        List<Line> lines = new List<Line>();

        double R1 = 2, R2 = 5;
        public void AddShape(Canvas canvas, Shape shape, EllipseStyle type)
        {

        }

        /// <summary>
        /// 在指定的Canvas上生成若干个点。
        /// </summary>
        /// <param name="canvas"></param>
        /// <param name="count"></param>
        public void GenerateNodes(Canvas canvas, int count = 100)
        {
            for (int i = 0; i < count; i++)
            {
                var ellipse = new Ellipse();
                ellipse.Tag = 0;
                common.SetStyle(ellipse, EllipseStyle.Point);
                SetPosition(ellipse, GetRandomPoint());
                points.Add(ellipse);
                canvas.Children.Add(ellipse);
            }
        }

        private void SetPosition(Ellipse e, Point pos)
        {
            Canvas.SetLeft(e, (int)pos.X);
            Canvas.SetTop(e, (int)pos.Y);
        }


        public Point GetRandomPoint()
        {
            return new Point(random.Next(20, (int)canvas.ActualWidth - 20), random.Next(20, (int)canvas.ActualHeight - 20));
        }

        public Line CreateLine(Ellipse p1, Ellipse p2)
        {
            var line = new Line();
            line.X1  = Canvas.GetLeft(p1) + p1.ActualWidth/2;
            line.Y1  = Canvas.GetTop(p1)+ p1.ActualHeight/2;
            line.X2  = Canvas.GetLeft(p2)+ p2.ActualWidth/2;
            line.Y2  = Canvas.GetTop(p2)+ p2.ActualHeight/2;
            line.Stroke = Brushes.Black;
            line.StrokeThickness = 0.5;
            return line;
        }


        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            for (int i = 0; i < 100; i++)
            {
                var point = new Ellipse();
                point.Tag = 0;
                common.SetStyle(point, EllipseStyle.Point);
                SetPosition(point, GetRandomPoint());
                points.Add(point);
                canvas.Children.Add(point);
            }

        }

        private void btnAddCentroids_Click(object sender, RoutedEventArgs e)
        {
            // remove all centroids
            foreach (var item in centroids)
            {
                if (item.Tag is Ellipse e1)
                    canvas.Children.Remove(e1);
                canvas.Children.Remove(item);
            }
            centroids.Clear();

            int k = (int)slide.Value;
            for (int i = 0; i < k; i++)
            {
                var point = new Ellipse();
                common.SetStyle(point, EllipseStyle.Centroid);
                SetPosition(point, GetRandomPoint());
                centroids.Add(point);
                canvas.Children.Add(point);
            }
            btnClustering.IsEnabled = true;
        }

        private void btnClustering_Click(object sender, RoutedEventArgs e)
        {
            Storyboard storyBoard = new Storyboard();
            double timeline = 0; // 当前时间
            if (lines.Count == 0)
            {

                foreach (var p in points)
                {
                    var line = CreateLine(Canvas.GetLeft(p) + R1, Canvas.GetTop(p) + R1, Canvas.GetLeft(p) + R1, Canvas.GetTop(p) + R1, Brushes.Black, 1);
                    canvas.Children.Add(line);
                    lines.Add(line);

                    var nearest = GetNearest(p, centroids);
                    p.Tag = nearest;
                    line.Tag = p;

                    timeline = GetTotalTime(storyBoard);
                    SetTargetToStoryboard(storyBoard, CreateDoubleAnimation(line.X1, Canvas.GetLeft(nearest)+ R2, 0.01, timeline), line, Line.X1Property);
                    SetTargetToStoryboard(storyBoard, CreateDoubleAnimation(line.Y1, Canvas.GetTop(nearest)+ R2, 0.01, timeline), line, Line.Y1Property);
                }
            }
            else
            {
                foreach (var line in lines)
                {
                    if (line.Tag is Ellipse ellipse)
                    {
                        var nearest = GetNearest(ellipse, centroids);
                        bool changed = ellipse.Tag != nearest;
                        ellipse.Tag = nearest;
                        line.Tag = ellipse;

                        GetTotalTime(storyBoard);

                        SetTargetToStoryboard(storyBoard, CreateDoubleAnimation(line.X1, Canvas.GetLeft(nearest)+ R2, changed ? 1 : 0.01, timeline), line, Line.X1Property);
                        SetTargetToStoryboard(storyBoard, CreateDoubleAnimation(line.Y1, Canvas.GetTop(nearest)+ R2, changed ? 1 : 0.01, timeline), line, Line.Y1Property);
                    }
                }
            }

            storyBoard.Begin(this);
        }

        private Line CreateLine(double X1, double Y1, double X2, double Y2, SolidColorBrush stroke, int strokeThick)
        {
            Line line = new Line();
            line.X1 = X1;
            line.Y1 = Y1;
            line.X2 = X2;
            line.Y2 = Y2;
            line.Stroke = stroke;
            line.StrokeThickness = strokeThick;
            return line;
        }

        private Ellipse GetNearest(Ellipse p, List<Ellipse> centroids)
        {
            if (centroids == null || centroids.Count == 0)
                return new Ellipse();

            Ellipse nearest = centroids[0];
            double distance = GetDistance(p, centroids[0]);
            for (int i = 1; i < centroids.Count; i++)
            {
                var di = GetDistance(p, centroids[i]);
                if (di < distance)
                {
                    distance = di;
                    nearest = centroids[i];
                }
            }
            return nearest;
        }

        private double GetDistance(Ellipse p1, Ellipse p2)
        {
            double x1 = Canvas.GetLeft(p1);
            double x2 = Canvas.GetLeft(p2);
            double y1 = Canvas.GetTop(p1);
            double y2 = Canvas.GetTop(p2);
            return Math.Sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
        }

        private void btnUpdateCentroids_Click(object sender, RoutedEventArgs e)
        {
            if (centroids.Count == 0)
                return;

            var storyBoard = new Storyboard();
            foreach (var c in centroids)
            {
                List<Ellipse> ellipses = GetEllipses(c, canvas);
                Point newCenter = GetCenter(ellipses);
                Point oldCenter = new Point(Canvas.GetLeft(c), Canvas.GetTop(c));

                if (c.Tag == null)
                {
                    var lastpos = CloneEllipse(c);
                    lastpos.Fill = Brushes.Transparent;
                    lastpos.StrokeThickness = 3;

                    canvas.Children.Add(lastpos);
                    c.Tag = lastpos;
                }

                if (c.Tag is Ellipse ellipse)
                {
                    Canvas.SetLeft(ellipse, Canvas.GetLeft(c));
                    Canvas.SetTop(ellipse, Canvas.GetTop(c));
                }

                SetTargetToStoryboard(storyBoard, CreateDoubleAnimation(oldCenter.X, newCenter.X + R2, 1), c, LeftProperty);
                SetTargetToStoryboard(storyBoard, CreateDoubleAnimation(oldCenter.Y, newCenter.Y + R2, 1), c, TopProperty);
            }

            storyBoard.Begin(this);

        }

        private Ellipse CloneEllipse(Ellipse ellipse)
        {
            Ellipse e = new Ellipse();
            e.Width = ellipse.ActualWidth;
            e.Height = ellipse.ActualHeight;
            e.Fill = ellipse.Fill;
            e.Stroke = ellipse.Stroke;
            e.StrokeThickness = ellipse.StrokeThickness;
            return e;
        }

        private Ellipse AddNode(Canvas canvas, EllipseStyle contour, Point pos)
        {
            var ellipse = new Ellipse();
            common.SetStyle(ellipse, EllipseStyle.Centroid);
            SetPosition(ellipse, pos);
            centroids.Add(ellipse);
            canvas.Children.Add(ellipse);
            return ellipse;
        }

        private Point GetCenter(List<Ellipse> ellipses)
        {
            if (ellipses == null || ellipses.Count <=0)
                return new Point();

            double x = 0, y = 0;
            foreach (var e in ellipses)
            {
                x += Canvas.GetLeft(e);
                y += Canvas.GetTop(e);
            }
            return new Point(x / ellipses.Count, y / ellipses.Count);
        }

        private List<Ellipse> GetEllipses(Ellipse c, Canvas canvas)
        {
            List<Ellipse> ellipses = new List<Ellipse>();
            foreach (var item in canvas.Children)
                if (item is Ellipse e1 && e1.Tag == c)
                    ellipses.Add(e1);
            return ellipses;
        }

        private void btnGenerateRandomNodes_Click(object sender, RoutedEventArgs e)
        {
            canvas.Children.Clear();
            centroids.Clear();
            lines.Clear();
            points.Clear();
            GenerateNodes(canvas, (int)slidePoints.Value);
            btnClustering.IsEnabled = false;
        }

        private void btnAddLinesAnimated_Click(object sender, RoutedEventArgs e)
        {
            var storyBoard = new Storyboard();
            storyBoard.Completed += StoryBoard_Completed;

            double timeline = 0; // 当前时间
            timeline += SetTargetToStoryboard(storyBoard, CreateDoubleAnimation(1, 0), "btnTest", Button.OpacityProperty);
            timeline +=SetTargetToStoryboard(storyBoard, CreateDoubleAnimation(0, 1, 1, timeline), "btnTest", Button.OpacityProperty);
            storyBoard.Begin(this);
        }

        private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == System.Windows.Input.Key.F1)
            {
                PerformClick(btnGenerateRandomNodes);
            }
            else if (e.Key == System.Windows.Input.Key.F2)
            {
                PerformClick(btnAddCentroids);
            }
            else if (e.Key == System.Windows.Input.Key.F3)
            {
                PerformClick(btnClustering);
            }
            else if (e.Key == System.Windows.Input.Key.F4)
            {
                PerformClick(btnUpdateCentroids);
            }
        }

        private void StoryBoard_Completed(object? sender, EventArgs e)
        {
            Debug.WriteLine("Storyboard done.");
        }
    }

    public enum EllipseStyle
    {
        Point,
        Centroid,
        Contour
    }
}
